package com.zslibrary.Pipe;

import android.os.SystemClock;
import android.util.Log;

import androidx.annotation.Nullable;

import com.zslibrary.Enum.BoardId;
import com.zslibrary.Enum.CommunicationError;
import com.zslibrary.Enum.PipeResultDefine;
import com.zslibrary.Enum.Protocol;
import com.zslibrary.Parse.ParseMessage;
import com.zslibrary.Structure.GeneralRequestStruct;
import com.zslibrary.Structure.MessageStruct;
import com.zslibrary.Structure.ReceiverStruct;
import com.zslibrary.Structure.ReplyStruct;
import com.zslibrary.Structure.RequestStruct;
import com.zslibrary.Tools.FrameBuilder;
import com.zslibrary.Tools.HexBytesUtil;
import com.zslibrary.Tools.SerialportUtil;
import com.zslibrary.Tools.ZSLog;

import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;

public class Pipe {
    private ReceiverStruct mRecvBuffer=new ReceiverStruct();
    private Boolean mNewDataFlag=false;
    private ReentrantLock pipeLock=new ReentrantLock(true);
    private SerialportUtil mSerialPort;
    private byte mBoardId;
    private Boolean mPipeInterrupt=false;
    private ParseMessage mParser=new ParseMessage();

    public  Boolean launchPipe(final String serialPort, @BoardId.BoardAdress int boardId, int baudRate){
        mSerialPort=new SerialportUtil();
        if(mSerialPort.openSerialport(serialPort,baudRate)){
            mRecvBuffer.data=new byte[4096];
            mSerialPort.setOnDataReceiveListender(new SerialportUtil.OnDataReceiveListender() {
                @Override
                public void onDataReceive(byte[] buffer, int size) {
                    System.arraycopy(buffer, 0, mRecvBuffer.data, mRecvBuffer.size, size);
                    mRecvBuffer.size += size;
                    mNewDataFlag=true;
//                   ZSLog.d( "port:"+serialPort+"context:"+ HexBytesUtil.byte2HexStr(buffer, size).toString());
                }
            });
            this.mBoardId=(byte)boardId;
            SystemClock.sleep(1000);
            return true;
        }
       return false;
    }

    private byte[] synchronousSend(FrameBuilder builder){
        try{
            pipeLock.lock();
            mSerialPort.sendSerialport(builder.toBytes(), builder.length());
            byte[] replyData=receiveBlock();
            if (replyData.length>0) {
                return replyData;
            }
        }catch(Exception e){
            ZSLog.d(e.toString());
        }finally {
            if (pipeLock.isHeldByCurrentThread())
                pipeLock.unlock();
        }
        return null;
    }

    private ReplyStruct basePipe(GeneralRequestStruct request,Boolean isPoll){
        byte[] replyData=null;
        ReplyStruct reply;
        byte tryTimes=0;
        RequestStruct sendRequest=new RequestStruct();
        sendRequest.boardId=this.mBoardId;
        sendRequest.gRequest=request;
        FrameBuilder builder=mParser.PackSendData(sendRequest); /*将请求数据包装为16进制数组*/
        do{
            //if(tryTimes>0)
                //ZSLog.d("pipe retry:" + String.valueOf(tryTimes));
            replyData=synchronousSend(builder);
            if(replyData==null){
                /*发送接收失败*/
                SystemClock.sleep(1000);
                continue;
            }
            reply=mParser.parseReceiveData(mBoardId,replyData,!isPoll);
            if(reply==null)
            {
                /*解析失败*/
                SystemClock.sleep(1000);
                continue;
            }
            /*return code只要success和busy两种需要处理*/
            if(reply.returnCode==CommunicationError.E_BUSY||reply.returnCode==CommunicationError.E_SUCCESS){
                return reply;
            }
            //ZSLog.d("pipe error:"+reply.returnCode);
            SystemClock.sleep(1000);
            continue;
        }while(++tryTimes<10);
        return null;
    }

    /***
     * @methodName: synchronousPipe
     * @description: 同步通信管道
     * @param: 同步请求request
     * @return: 响应，可能包含多条message，需要自行处理return code，如果为null，表示通信异常
     ***/
//    public ReplyStruct synchronousPipe(GeneralRequestStruct request){
//        return basePipe(request,false);
//    }
    public @PipeResultDefine.PipeResult int synchronousPipe(GeneralRequestStruct request,@Nullable HandleMessageCallback handler){
        ReplyStruct reply=basePipe(request,false);
        if(reply==null){
            ZSLog.d("sync pipe null");
            return PipeResultDefine.SYNC_ERROR;
        }
        if(reply.returnCode==CommunicationError.E_BUSY){
            ZSLog.d("async pipe busy");
            return PipeResultDefine.ASYNC_BUSY;   /*通信忙*/
        }
        if(handler==null)   /*可空参数*/
            return PipeResultDefine.SYNC_OK;
        if(reply.messages.length>0){
            try{        /*防止数据错误解析崩溃*/
                handler.handle(reply.messages);
            }catch (Exception e){
                ZSLog.d(e.toString());
                return PipeResultDefine.SYNC_ERROR;
            }
        }
        return PipeResultDefine.SYNC_OK;
    }

    /***
     * @methodName: synchronousPipe
     * @description: 轮询通信管道
     * @param: 同步请求request
     * @return: 响应，可能包含多条message，需要自行处理return code，如果为null，表示通信异常
     ***/
    public ReplyStruct pollPipe(GeneralRequestStruct request){
        return basePipe(request,true);
    }
    /***
     * @methodName: asynchronousPipe
     * @description: 异步通信管道
     * @param: 异步请求request;超时时间;异步处理回调
     * @return: 通信过程是否正常或者是否被中断
     ***/
    public @PipeResultDefine.PipeResult int asynchronousPipe(GeneralRequestStruct request, HandleMessageCallback handler,int timeOut){
        long startTime=System.currentTimeMillis();
        ReplyStruct reply=basePipe(request,false); /*发送同步指令*/
        GeneralRequestStruct asyncRequest=new GeneralRequestStruct();
        if(reply==null){
            ZSLog.d("async pipe null");
            return PipeResultDefine.ASYNC_ERROR;   /*通信异常*/
        }
        if(reply.returnCode==CommunicationError.E_BUSY){
            ZSLog.d("async pipe busy");
            return PipeResultDefine.ASYNC_BUSY;   /*通信忙*/
        }
        asyncRequest.requstId= Protocol.MSG_POLL;
        asyncRequest.dataLength=0;
        while(!mPipeInterrupt){
            SystemClock.sleep(200);
            reply=basePipe(asyncRequest,true);
            if(reply==null){
                return PipeResultDefine.ASYNC_ERROR;    /*通信异常*/
            }
            if(reply.returnCode==CommunicationError.E_BUSY) {
                ZSLog.d( "async pipe busy");
                return PipeResultDefine.ASYNC_BUSY;   /*通信忙*/
            }
            if(reply.messages.length>0){
                if(handler.handle(reply.messages)){
                    return PipeResultDefine.ASYNC_OK;  /*通信正常*/
                }
            }
            if((timeOut>0)&&(System.currentTimeMillis()-startTime>timeOut)) {
                ZSLog.d("async pipe timeout");
                return PipeResultDefine.ASYNC_TIMEOUT;  /*通信超时*/
            }
        }
        mPipeInterrupt=false;
        return PipeResultDefine.ASYNC_INTERRUPT;   /*通信被中断*/
    }

    public void interruptAsynchonousPipe(){
        mPipeInterrupt=true;
    }

    public interface HandleMessageCallback{
        Boolean handle(MessageStruct[] messages);
    }

    private byte[] receiveBlock(){
        long startTime=System.currentTimeMillis();
        FrameBuilder builder=null;
        Boolean coming=false;
        int dataLen=0;
        Boolean result=false;
        byte[] returnData;

        while(System.currentTimeMillis()-startTime<200){
            SystemClock.sleep(1);
            if(mNewDataFlag){
                mNewDataFlag=false;
                if(!coming){
                    coming=true;
                    builder=new FrameBuilder(mRecvBuffer.data);
                    builder.popData(2);
                    dataLen=builder.popData(2);
                }
                if(dataLen+5==mRecvBuffer.size){
                    result=true;
                    break;
                }
                startTime=System.currentTimeMillis();
            }
        }
        returnData= Arrays.copyOf(mRecvBuffer.data,mRecvBuffer.size);
        mRecvBuffer.size=0;
        if(result==false) {
            if(returnData.length!=dataLen+5){
                ZSLog.d("receive failed");
                return new byte[0];
            }
        }
        return returnData;
    }
}
